home *** CD-ROM | disk | FTP | other *** search
/ ftp.mactech.com 2010 / ftp.mactech.com.tar / ftp.mactech.com / challenge / 13.10 / ChallengeDisambig.sit.hqx / Challenge, Disambiguator / disambig.cp
Text File  |  1997-08-22  |  32KB  |  1,117 lines

  1. /*
  2.   July 1, 1997.
  3.   Submission to MacTech Programmer's challenge for July 97.
  4.   Copyright 1997, Ernst Munter, Kanata, ON, Canada.
  5.  
  6.           "DISAMBIGUATOR"
  7.           
  8. Version 2: minor tweak to gain 2 to 3% in speed
  9. Version 3: major tweak: gain 20% in speed
  10.                         added length-byte to page data,
  11.                         avoids unneeded calls to strlen()
  12.  
  13. Problem Statement
  14. -----------------
  15. Given a list of words and a findString, find all matching
  16. words which start with findString.  FindString may contain
  17. wild cards ? * and +.
  18.  
  19. An initialization routine can be used to build a private
  20. data structure to speed up searching.
  21.  
  22. The initial word list may or may not be sorted, but each
  23. generated match list should be sorted.
  24.  
  25. Solution Strategy
  26. -----------------
  27. A string matching engine is needed to evaluate words from
  28. the word list against the find string.
  29.  
  30. In the simplest case, the match list can be created by
  31. comparing each word in the word list with the find string,
  32. and then sorting match list.  This is the procedure I
  33. use when there is insufficient storage available (which
  34. may happen if the word list is very short).
  35.  
  36. When there is enough storage available, a private index is
  37. derived from the word list.  This is based on word length
  38. as well as bit maps representing 64-bit "digests" of words.
  39.  
  40. On searching with a particular find string, only words of
  41. at least the known minimum length need be considered.
  42.  
  43. The find string is also represented with a digest.
  44. The digest does not describe a word uniquely, and a final
  45. direct comparison is needed to confirm a word for the
  46. match list.  But the digest representation helps in
  47. filtering out many words and groups of words quickly that
  48. need not be compared.
  49.  
  50. Alphabetical sorting is done on each generated match list.
  51.  
  52. The String Compare Engine
  53. -------------------------
  54. There are two possibilities:
  55.  - findString contains wild cards
  56.  - or it does not
  57.  
  58. If it does, string matching will be performed by a
  59. virtual machine executing a very simple byte code program;
  60. however, if there are no wild cards, an even simpler
  61. comparison routine is used.
  62.  
  63. In any case, the Parser function processes the input
  64. string (findString) into a program and an output string.
  65. The output string is simply the input string with wild
  66. cards removed.
  67.  
  68. The first value in the program is the minimum length of
  69. any string required to match the original findString.
  70. The subsequent program bytes are from the set of
  71. instructions
  72.  - READ  n    read n chars without comparing
  73.  - COMPF n    compare n chars,
  74.              on mismatch exit with FAIL
  75.  - COMPR n  compare n chars,
  76.              on mismatch go back (n-1) and retry
  77.              
  78. The program always ends with SUCCEED
  79.  
  80. Special single byte forms of READ1 and COMPn (n<=7)
  81. optimize for common cases (I hope), and also ensure that
  82. the length of the program is guaranteed not to exceed the
  83. length of findString.  Since strlen(findString) is known
  84. to be < 256, a fixed amount of memory can be provided for
  85. the program on the CPU stack.
  86.  
  87. Private Data Organization
  88. -------------------------
  89. All words of the original word list are represented
  90. grouped in pages of 32 (max) words of equal length.
  91. Words of length > 31 are not differentiated and are
  92. lumped in with pages of 31-char words.
  93.  
  94. Each page contains pointers to 32 words, their
  95. individual word digests, and a summarizing page digest.
  96.  
  97. Word Digest
  98. -----------
  99. Two 32-bit words (28 bits used), one for single, the
  100. other for multiple character occurrences in a word.
  101.  
  102. Digest-1 of a wordList word is a 32-bit computer word,
  103. each bit indicating the presence or not of a character
  104. value in the wordList word.
  105.  
  106. Bits in digest-2 are set only if 2 or more of a
  107. particular character occur in the word.
  108.  
  109. The hash function to convert a character into a bit
  110. position is tabulated in charTable[128] which maps all
  111. legal characters into the range 1 to 28.  The values in
  112. charTable are roughly in order of frequency of occurrence
  113. of letters in an English dictionary.
  114.  
  115. The digest for a find string is based on the non-wild
  116. cards in the string, and thus will be a subset of any
  117. match word matching the find string.
  118.  
  119. Pages
  120. -----
  121. The example below shows an excerpt from a typical page
  122. based on the dictionary I used for a word list.
  123.  
  124. Each row represents the word digest for the referenced
  125. word at the end of the row.
  126.  
  127. The last row is the page digest, the bit-OR of the columns.
  128.  
  129. Note that for reasons explained below, storage of the word
  130. digests is by column, that is the bits shown below are stored
  131. in 56 ulongs, each column representing the presence single or
  132. multiple, of a particular character in all words of the page.
  133.  
  134.     - single -                    - multiple -
  135. _ qjxzkwvfybhmgpudclotransie= QJXZKWVFYBHMGPUDCLOTRANSIE
  136. 00000000000000001000010111100000000000000000000000001010 Tunisian
  137. 00000000000000001000010111100000000000000000000000000100 sustains
  138. 00000000000011001000010111100000000000000000000000000000 humanist
  139. 00000000000000011000010111100000000000000000000001000000 pantsuit
  140. 00000000000000011000010111100000000000000000000000000100 puissant
  141. 10000000000000000100010111100000000000000000000000001000 stand_in
  142. 00000000000000100100010111100000000000000000000000001000 standing
  143. 00000000010000000010010111100000000000000000000000010000 fanatics
  144. 00000000001000000010010111100000000000000000000001000000 sanctity
  145. 00000000011000000010010111100000000000000000000000000000 sanctify
  146. 00000000000000100010010111100000000000000000000000000100 castings
  147. 00000000000000100010010111100000000000000000000001000000 scatting
  148. 00000010000000100010010111100000000000000000000000000000 stacking
  149. 00000000000010100010010111100000000000000000000000000000 scathing
  150. 00000000000000010010010111100000000000000000000000010000 captains
  151. 00000000000000000110010111100000000000000000000000010000 antacids
  152. 00000000000000000001010111100000000000000000000000000010 initials
  153. 00000000000000000001010111100000000000000000000100000100 installs
  154. 00000000000000000001010111100000000000000000000000010010 Italians
  155. 00000000010000000001010111100000000000000000000000000010 finalist
  156. 00000000001000000001010111100000000000000000000000000010 salinity
  157. 00000000000000100001010111100000000000000000000000001000 slanting
  158. 00000000000000100001010111100000000000000000000100000000 stalling
  159. 00000000000000100001010111100000000000000000000000000010 tailings
  160. 00000010000000100001010111100000000000000000000000000000 stalking
  161. 00000000000100100001010111100000000000000000000000000000 blasting
  162. 00000000000100100001010111100000000000000000000000000000 stabling
  163. 00000000000000010001010111100000000000000000000000000010 tailspin
  164. 00000000000000110001010111100000000000000000000000000000 stapling
  165. 00000000000100001001010111100000000000000000000000000000 Istanbul
  166. 00000000000000101001010111100000000000000000000000000000 saluting
  167. 00000000000000011001010111100000000000000000000000000000 nuptials
  168. page digest:
  169. 10000010011111111111010111100000000000000000000101011110
  170.  
  171. Before assembling the words into pages, a private
  172. temporary word list is built, sorted on digest1.  This
  173. groups words of similar digests into a page, resulting in
  174. sparser, more effective bit patterns for the page digests.
  175.  
  176. Word Filtering
  177. --------------
  178. To find matching words we use the page index to skip past
  179. all pages containing words of less than the minimum length.
  180.  
  181. The page index scan then provides a fast screening function
  182. by comparing the find digest with each page digest (a copy
  183. of which exists in the index entry).
  184.  
  185. Once a promising page is identified on the basis of its
  186. digest, it is scanned to find matching words.
  187.  
  188. The findString generates a signature, a string of unique
  189. hash values of the original findString. To scan the digest
  190. bits in a page for a particular findString, the signature
  191. values are used to select columns of bits.  These are
  192. accumulated (bitwise AND).  The resulting 32-bit value
  193. identifies, by 1-bit positions, the words matching the
  194. findString as far as digests go.  The accumulation of
  195. columns is abandoned as soon as the accumulator reads 0,
  196. indicating that no words on the page will match.
  197.  
  198. If the accumulator pattern is not 0, one or several words
  199. may be indicated, but they still need to be confirmed with
  200. the alpha-numerical string matcher before being added to
  201. the match list.
  202.  
  203. Sorting
  204. -------
  205. Sorting of the final match list is done using a form of
  206. HeapSort, split into its two components, building of the
  207. priority queue (my routine Send()) and down sifting (my
  208. routine Sort()).
  209.  
  210. Special Cases
  211. -------------
  212. If findString contains ONLY wild cards, there is no need
  213. to match any words except for length.  Since word pages
  214. are already organized by word length, it remains to send
  215. all words from all pages of the minimum length given by
  216. findString (the number of '+' and '?' symbols).
  217.  
  218. Optimizations
  219. -------------
  220. When there are no wild cards in findString, a simpler and
  221. faster alphanumeric comparison is used.
  222.  
  223. The pageIndex entries contain a copy of the page digest1.
  224. This avoids memory access to many pages which evidently
  225. contain no matching words.
  226.  
  227. The hash function is chosen to represent character
  228. frequency in order to cluster words in pages more
  229. optimally.  This feature relies on presorting the private
  230. word list accordingly.  The program will work correctly if
  231. this sort is turned off, resulting in faster initialization,
  232. but slower matches.
  233.  
  234. In my tests, presorting paid off.
  235.  
  236. There is some amount of code replication for expediency,
  237. notably there are two customized copies of heap sort, and
  238. two versions of generating word digests.  These could
  239. probably avoided without a great loss of speed.
  240.  
  241. A final optimization is the way word signatures are constructed.
  242. These are derived from findString and used to scan the columns
  243. in the page bit maps.  Rather than convert the findString
  244. alphanum characters to signature characters (range 1-56) in
  245. the order of occurrence in findString, we arrange it so the
  246. rarest characters are tested first.
  247.  
  248. Assumptions
  249. -----------
  250. All words, and findString are < 256 characters long.
  251.  
  252. There are no zero-length words in wordList.
  253.  
  254. The absolute minimum amount of private storage is 4 bytes.
  255. This ensures that non-initialization of the tables can be
  256. determined.  In this mode, a sequential scan of wordList
  257. is made.
  258.  
  259. For optimal operation, memory available for private data
  260. should be at least 256 bytes + about 13 bytes per word.
  261. The exact amount of storage needed depends on the length
  262. distribution of the words in wordList.
  263.  
  264. FindString will be modified by Disambiguator.
  265.  
  266. Static memory of 384 bytes is used for lookup tables.
  267. */
  268.  
  269.  
  270. /**** the published header file ****/
  271. typedef unsigned long ulong;
  272.  
  273. void InitDisambiguator(
  274.   const char *const wordList[],  /* words to match against */
  275.   ulong numWords,        /* number of words in wordList */
  276.   void *privStorage,     /* private storage preinitialized to zero */
  277.   ulong storageSize      /* number of bytes of privStorage */
  278. );
  279.  
  280. ulong /*numMatch*/ Disambiguator(
  281.   const char *const wordList[],  /* words to match against */
  282.   ulong numWords,        /* number of words in wordList */
  283.   void *privStorage,     /* private storage */
  284.   ulong storageSize,     /* number of bytes of privStorage */
  285.   char *findString,      /* string to match, includes wild cards */
  286.   const char *matchList[]      /* return matched words here */
  287. );
  288. /**** end of published header file ****/
  289.  
  290.  
  291. #include <stdlib.h> // It seems unnecessary to mention
  292. #include <string.h> // these with CW-Pro
  293.  
  294. // NOTE:
  295. // Program Tuning Option
  296. // Turning SORTWORDINDEX off reduces initialization time
  297. //        to 1/3 but increases search time by 50 to 80%
  298. // The best setting depends on dictionary, findString
  299. //        patterns and the number of searches in a test run.
  300.  
  301. #define SORTWORDINDEX 1
  302.  
  303.  
  304. typedef unsigned char Opcode;
  305.  
  306. // 'const char' is used a lot.
  307. #define CCC const char
  308.  
  309. /*** Function prototypes ***/
  310. static int Parse(char* spec,Opcode program[]);
  311. static int SimpleMatch(CCC* x,char* spec,int minLen);
  312. static int VMMatch(CCC* x,char* spec,Opcode* program);
  313. static int Comp(CCC* ap,CCC* bp);
  314. static ulong MakeSubDigest(
  315.                 CCC* xString,ulong* dig2,char sig[]);
  316. static void Sort(CCC* matchList[],long numMatch);
  317. static void Send(CCC* matchList[],CCC* wp,ulong numMatch);
  318.  
  319. /*** Static allocations ***/
  320.  
  321. // charTable serves double duty:
  322. //        in parsing, it helps separate wild cards.
  323. //        in digest forming, it provides a sort order.
  324. static char charTable[128] = {
  325.  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  326.  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  327.  2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  328.  1, 2, 3, 4, 5, 6, 7, 8, 9,10, 0, 0, 0, 0, 0, 0,
  329.  0,25,12,19,18,28,10,16,13,27, 4, 7,20,14,23,21,
  330. 15, 3,24,26,22,17, 9, 8, 5,11, 6, 0, 0, 0, 0, 1,
  331.  0,25,12,19,18,28,10,16,13,27, 4, 7,20,14,23,21,
  332. 15, 3,24,26,22,17, 9, 8, 5,11, 6, 0, 0, 0, 0, 0
  333. };
  334.  
  335.  
  336. // The table LSB stores the position of the least
  337. // significant '1' bit in a byte (range 1 to 8),
  338. // a zero-byte reports 0.
  339. // This table is built from nested #defines
  340. #define T1 1
  341. #define T2 T1,2,T1
  342. #define T3 T2,3,T2
  343. #define T4 T3,4,T3
  344. #define T5 T4,5,T4
  345. #define T6 T5,6,T5
  346. #define T7 T6,7,T6
  347. #define T8 T7,8,T7
  348.  
  349. static char LSB[256]={0,T8};
  350.  
  351. // MISMATCH takes care of case insensitive matching
  352. #define MISMATCH(a,b) ((a^b)&0xDF)
  353.  
  354. #define MIN(a,b) (a<b?a:b)
  355.  
  356. // HASH is just shorthand for charTable.
  357. #define HASH(c) (charTable[c])
  358.  
  359. // Maximum length of strings
  360. #define MAX_LEN 256
  361.  
  362. const enum {                    //compr must be last
  363. SUCCEED=0,FAIL,RETRY,READ,READ1,COMPF,COMPR=COMPF+8};
  364.  
  365. static int VMMatch(CCC* x,int slen,char* spec,Opcode* program) {
  366. // Virtual machine interpreter.
  367. // VMMatch executes "program" with the help of
  368. // "spec" to determine if string "x" matches.
  369.   int margin=slen-*program;
  370.   if (margin<0) return FAIL;
  371.   CCC* saveX;
  372.   char* saveSpec;
  373.   Opcode* savePC=0;
  374.   Opcode* pgm=program;
  375.   int matchLen,i;
  376.   x--;spec--;
  377.   for (;;) {
  378.     Opcode op=*++pgm;
  379.     switch (op) {
  380. case SUCCEED:
  381. case FAIL:
  382.       return op;
  383. case COMPF+7: if (MISMATCH(*++x,*++spec)) goto retry;
  384. case COMPF+6: if (MISMATCH(*++x,*++spec)) goto retry;
  385. case COMPF+5: if (MISMATCH(*++x,*++spec)) goto retry;
  386. case COMPF+4: if (MISMATCH(*++x,*++spec)) goto retry;
  387. case COMPF+3: if (MISMATCH(*++x,*++spec)) goto retry;
  388. case COMPF+2: if (MISMATCH(*++x,*++spec)) goto retry;
  389. case COMPF+1: if (MISMATCH(*++x,*++spec)) goto retry;
  390.       break;
  391. case COMPF:
  392.       matchLen=*++pgm;
  393.       for (i=0;i<matchLen;i++) {
  394.         if (MISMATCH(*++x,*++spec)) goto retry;
  395.       }
  396.       break;
  397. retry:
  398. // this mysterious retry after mismatch takes care of
  399. // "*x?y" to find word "xxay", matching the second x
  400.       if ((savePC==0)||(0==margin--)) return op;
  401.       pgm=savePC-1;
  402.       x=saveX+1;
  403.       spec=saveSpec;
  404.       break;
  405. case COMPR+7:
  406. case COMPR+6:
  407. case COMPR+5:
  408. case COMPR+4:
  409. case COMPR+3:
  410. case COMPR+2:
  411. case COMPR+1:
  412.       savePC=pgm;
  413.         matchLen=op-COMPR;
  414.         goto try_again;
  415. case COMPR:
  416.         savePC=pgm;
  417.         matchLen=*++pgm;
  418. try_again:
  419.         for (i=1;i<=matchLen;i++) {
  420.         if (MISMATCH(x[i],spec[i])) {
  421.           if (margin--) {
  422.             ++x;
  423.             goto try_again;
  424.             }
  425.             return op;
  426.         }
  427.         }
  428.         saveX=x;
  429.       saveSpec=spec;
  430.       x+=matchLen;
  431.       spec+=matchLen;
  432.       break;
  433. case READ:
  434.       x+=*++pgm;
  435.       break;
  436. case READ1:
  437.       ++x;
  438.       break;
  439. default:
  440.       return op;
  441.     }
  442.   }
  443. }
  444.  
  445. // The class WordIndex holds a pointer to a word from
  446. // wordList and computes digests and signature for it.
  447. struct WordIndex {
  448.   ulong digest1;
  449.   CCC* word;
  450.  
  451.   void Init(CCC* wordx) {
  452.     CCC* wp=wordx;
  453.     int s=HASH(*wp);
  454.     ulong dig1=1L<<s;
  455.     for (;;) {
  456.       int c;
  457.       if (0==(c=*++wp)) break;
  458.       s=HASH(c);
  459.       ulong bit=1L<<s;
  460.       dig1 |= bit;
  461.     }
  462.     digest1=dig1;
  463.     word=wordx;
  464.   }
  465.  
  466.   ulong MultiDigest(char* sig) {
  467. // Returns digest2 for the word,
  468. // and computes its signature
  469.     CCC* wp=word;
  470.     int s=HASH(word[0]);
  471.     ulong digest2=0;
  472.     ulong dig1=1L<<s;
  473.     sig[0]=s;
  474.     for (;;) {
  475.       int c;
  476.       if (0==(c=*++wp)) break;
  477.       s=HASH(c);
  478.       ulong bit=1L<<s;
  479.       if (0==(dig1 & bit)) {
  480.         *++sig=s;
  481.         dig1 |= bit;
  482.       } else if (0==(digest2 & bit)) {
  483.         *++sig=s+28;
  484.         digest2 |= bit;
  485.       }
  486.     }
  487.     *++sig=0;
  488.     return digest2;
  489.   }
  490.   int CompDigest(WordIndex wx) {
  491.     return (wx.digest1)-(digest1);
  492.   }
  493. };
  494.  
  495. // The class Page holds pointers to 32 words from wordList
  496. // which are of the same length (words >= 31 together).
  497. // Page also contains both word digests for each word,
  498. // in the bits[] array, stored in signature oriented columns.
  499. // A page provides string matching for the 32 words it owns.
  500. struct Page{
  501.   CCC*     word[32];
  502.   char    len[32];
  503.   int      fill;
  504.   Page* next;
  505.   ulong pageDigest1;
  506. //  ulong pageDigest2; overlay on unused bits[0]
  507. #define pageDigest2 bits[0]
  508.   ulong bits[57];            
  509.       
  510.   void Init(Page* following) {
  511. // clears all and sets linkage.
  512. // memory may already be precleared, but not necessarily
  513. // since pages may overlay the temporary wordIndex
  514.     memset(this,0,sizeof(Page));
  515.     next=following;
  516.   }
  517.  
  518.   int IsFull() {return (fill>=32);}
  519.  
  520.   void Add(WordIndex* wip,int length) {
  521.   // Adds one word to a page,
  522.   // ORs the word digests into the page digests,
  523.   // Also ORs the horizontal bit slice representing word digests
  524.   //     into the bits array.
  525.     char sig[64];
  526.     ulong curbit=1L<<fill;
  527.     len[fill]=length;
  528.     word[fill++]=wip->word;
  529.     pageDigest1 |= wip->digest1;
  530.     pageDigest2 |= wip->MultiDigest(sig);
  531.     int c;
  532.     char* sigp=sig;
  533.     while (0 !=(c=*sigp++)) {
  534.       bits[c] |= curbit;
  535.     }
  536.   }
  537.  
  538.   ulong Match(char sig[])
  539.   // Accumulates vertical bit slices from a given signature
  540.   // Returns a bit map of likely candidate words
  541.   {
  542.     int c=sig[0];
  543.     ulong acc=bits[c];
  544.     while ((acc) && (0 != (c=*++sig))) {
  545.       acc &= bits[c];
  546.     }
  547.     return acc;
  548.   }
  549.  
  550.   ulong SendSelectionVM(
  551.         ulong acc,
  552.         char* findString,
  553.         Opcode program[],
  554.         CCC* matchList[],
  555.         ulong numMatch)
  556.   {
  557.   // Uses the "acc" bitmap to identify words for full
  558.   // string matching, and sends matching words to the matchList
  559.   // Uses the LSB array to quickly isolate bits in acc.
  560.     CCC**    wp=word-1;
  561.     char*    lenp=len-1;
  562.     do {
  563.       ulong accLo=acc & 0xFF;
  564.       if (accLo) {
  565.         int j=LSB[accLo];
  566.         acc>>=j;
  567.         wp+=j;lenp+=j;
  568.         if (SUCCEED==VMMatch(*wp,*lenp,findString,program))
  569.           Send(matchList,*wp,numMatch++);
  570.       } else {
  571.         acc>>=8;
  572.         wp+=8;
  573.       }
  574.     } while (acc);
  575.     return numMatch;
  576.   }
  577.  
  578.   ulong SendSelection(
  579.         ulong acc,
  580.         char* findString,
  581.         Opcode program[],
  582.         CCC* matchList[],
  583.         ulong numMatch)
  584.   {
  585.   // Same as SendSelectionVM, but uses simpler string matching
  586.     CCC** wp=word-1;
  587.     int minLen=program[0];
  588.     do {
  589.       ulong accLo=acc & 0xFF;
  590.       if (accLo) {
  591.         int j=LSB[accLo];
  592.         acc>>=j;
  593.         wp+=j;
  594.         if (SUCCEED==SimpleMatch(*wp,findString,minLen))
  595.           Send(matchList,*wp,numMatch++);
  596.       } else {
  597.         acc>>=8;
  598.         wp+=8;
  599.       }
  600.     } while (acc);
  601.     return numMatch;
  602.   }
  603.  
  604.   ulong SendAll(CCC* matchList[],ulong numMatch) {
  605.   // Sends all words on page to matchList
  606.     for (int i=0;i<fill;i++)
  607.       Send(matchList,word[i],numMatch++);
  608.     return numMatch;
  609.   }
  610. };
  611.  
  612.  
  613. // The class PageIndex contains a pointer to a page, and
  614. // keeps a copy of the page digest1.
  615. // During the scan, PageIndex provides a screening function
  616. // to eliminate unnecessary page accesses if digest1 can
  617. // already rule out all words on a page.
  618. struct PageIndex {
  619.   ulong digest1;    
  620.   Page* page;                
  621.   void Init(Page* thePage) {
  622.     digest1=thePage->pageDigest1;
  623.     page=thePage;
  624.   }
  625.   Page* Screen(ulong findDigest1,ulong findDigest2) {
  626.     if ((findDigest1 ^ (findDigest1 & digest1)))
  627.       return 0;
  628.     return page;
  629.   }
  630. };
  631.  
  632.  
  633. // The class PrivateData is the main structure mapped into the
  634. // private memory space allocated on the heap.
  635. // The pageGroup[] array holds pointers to linked lists of
  636. //        pages, according to word length.
  637. // Once all pages are assembled, the page addresses are remapped
  638. //         into a linear page index, sorted by ascending word length
  639. // The indexGroup[i] array points to the the first page of each
  640. //        group of pages of a given word length i.
  641. struct PrivateData {
  642. // Page* nextPage; overlay on unused pageGroup[0]
  643. #define nextPage pageGroup[0]
  644.   Page*  pageGroup[32];
  645. // PageIndex* endOfPageIndex; overlay on indexGroup[0]
  646. #define endOfPageIndex indexGroup[0]
  647.   PageIndex* indexGroup[32];
  648.   int  bottom[1];
  649.  
  650.   void Init(
  651.           CCC * const wordList[],
  652.           ulong numWords,
  653.           ulong storageSize)
  654.   {
  655. // Must have at least this much storage to build minimal
  656. // page index system
  657.     ulong minimumStorage=
  658.       sizeof(pageGroup) +
  659.       sizeof(indexGroup) +
  660.       numWords*sizeof(WordIndex) +
  661.       sizeof(PageIndex) +
  662.       sizeof(Page);
  663.     if (storageSize<minimumStorage) {
  664.       nextPage=0;
  665.       return;
  666.     }
  667.  
  668. #if SORTWORDINDEX    
  669. // Build priority queue of word index items
  670. // This is the insertion step of heap sort
  671.     WordIndex* wordIndexList=(WordIndex*)bottom;
  672.     int i,j,n;
  673.     for (n=0;n<numWords;n++) {
  674.       WordIndex wx;
  675.       wx.Init(wordList[n]);
  676.       WordIndex* base=wordIndexList-1;
  677.         WordIndex z;
  678.       i=n+1,j=i>>1;
  679.       while ((j>0)&&(wx.CompDigest(z=base[j])>0)) {
  680.         base[i]=z;
  681.         i=j;j=i>>1;
  682.       }
  683.       base[i]=wx;
  684.     }
  685.  
  686. // Unload priority queue, step 2 of heap sort
  687.     nextPage=(Page*)this + storageSize/sizeof(Page) - 1;
  688.     WordIndex* wip;
  689.     WordIndex* base=wordIndexList-1;
  690.     WordIndex x;
  691.     ulong numUnsorted=numWords;
  692.     wip=base+numUnsorted+1;
  693.     if (numUnsorted>1) do {
  694.       i=1;j=2;
  695.       x=base[numUnsorted--];
  696.       *(--wip) = base[1];
  697.       if (numUnsorted<=1) {
  698.         base[1]=x;
  699.         break;
  700.       }
  701.       while (j<=numUnsorted) {
  702.         if ((j<numUnsorted)
  703.          && (base[j].CompDigest(base[j+1])<0))
  704.           j++;
  705.         if (x.CompDigest(base[j])>=0)
  706.           break;
  707.         base[i]=base[j];
  708.         i=j;j+=j;
  709.       }
  710.       base[i]=x;
  711.     } while(1);
  712. #else
  713. // No sorting, just copy and initialize word index
  714. // in whatever order wordList is in.
  715. // This reduces Initialization time at the expense
  716. // of search efficiency because more pages will
  717. // have to be filtered.
  718.     WordIndex* wordIndexList=(WordIndex*)bottom;
  719.     int n;
  720.     for (n=0;n<numWords;n++)
  721.       wordIndexList[n].Init(wordList[n]);
  722.  
  723.     WordIndex* wip;
  724. #endif
  725.  
  726. // Unload word index and build pages in linked lists
  727. // based on word length
  728.     nextPage=(Page*)this + storageSize/sizeof(Page) - 1;
  729.     wip=wordIndexList+numWords;
  730.     while (wip>wordIndexList) {
  731.       wip--;
  732.       if (0==InsertWordInPage(wip)) return;
  733.     }
  734.  
  735. // Map linked lists to a linear index of pages by length
  736.     if (0==BuildIndex()) {
  737.       nextPage=NULL;  // not enough storage
  738.       return;
  739.     }
  740.   }
  741.  
  742.   int InsertWordInPage(WordIndex* wip) {
  743. // Inserts one word in a page, opens a new page if
  744. // none exists or if the current page is full.
  745.     int len=strlen(wip->word);
  746.     if (len==0) return 1;  // ignore 0-length words
  747.     int cutLen=MIN(31,len);
  748.     Page* page=pageGroup[cutLen];
  749.     if ((page==0) || (page->IsFull())) {
  750.       Page* temp=page;
  751.       page=nextPage--;
  752. // test if the bottom of the growing page array collides
  753. // with the top of the shrinking word index array
  754.       if (page <= (Page*)wip) {
  755. // not enough storage, we have to bail out
  756.         nextPage=NULL;
  757.         return 0;
  758.       }
  759.       page->Init(temp);
  760.       pageGroup[cutLen]=page;
  761.     }
  762.     page->Add(wip,len);
  763.     return 1;
  764.   }
  765.  
  766.   PageIndex* BuildIndex() {
  767. // Builds the page index, starting at this->bottom,
  768. // overwriting storage previously used by word index.
  769.     PageIndex* pi=(PageIndex*)bottom;
  770.        PageIndex* piTop=(PageIndex*)nextPage;
  771.     for (int len=1;len<32;len++) {
  772.       Page* page=pageGroup[len];
  773.       indexGroup[len]=pi;
  774.       while (page) {
  775.         if (pi>=piTop) return 0;
  776.         pi++->Init(page);
  777.         page=page->next;
  778.       }
  779.     }
  780.     return (endOfPageIndex=pi);
  781.   }
  782.  
  783. // Use 'nextPage' as a flag to indicate if we initialized
  784. // nextPage will be NULL if we ran out of storage during
  785. // the initialization.
  786.   void* IsInitialized(){return nextPage;}
  787.  
  788.   ulong SendAll(CCC* matchList[],ulong minLen) {
  789. // Sends all words >= minimum length from all pages
  790.     ulong numMatch=0;
  791.     for (int len=MIN(31,minLen);len<32;len++) {
  792.       Page* page=pageGroup[len];
  793.       while (page) {
  794.         numMatch=page->SendAll(matchList,numMatch);
  795.         page=page->next;
  796.       }
  797.     }
  798.     return numMatch;
  799.   }
  800.  
  801.   ulong CollectVM(
  802.           char* findString,
  803.           CCC* matchList[],
  804.           Opcode program[])
  805.   {
  806. // CollectVM scans all pages above the minimum length,
  807. // matches using the virtual machine string matcher,
  808. // and sends matched words into matchList
  809.     char sig[64];
  810.     ulong findDigest2;
  811.     ulong findDigest1=MakeSubDigest(findString,&findDigest2,sig);
  812.     ulong numMatch=0;
  813.     PageIndex* pi=indexGroup[MIN(31,program[0])];
  814.     for (;pi<endOfPageIndex;pi++) {
  815.       Page* page=pi->Screen(findDigest1,findDigest2);
  816.       if (page) {
  817.         ulong acc=page->Match(sig);
  818.         if (acc) numMatch=page->SendSelectionVM(
  819.           acc,findString,program,matchList,numMatch);
  820.       }
  821.     }
  822.     return numMatch;
  823.   }
  824.  
  825.   ulong Collect(    
  826.         char* findString,
  827.         CCC* matchList[],
  828.         Opcode program[])
  829.   {
  830. // Collect is similar to CollectVM but uses the simpler
  831. // string matching function.
  832.     char sig[64];
  833.     ulong findDigest2;
  834.     ulong findDigest1=MakeSubDigest(findString,&findDigest2,sig);
  835.     ulong numMatch=0;
  836.     PageIndex* pi=indexGroup[MIN(31,program[0])];
  837.     for (;pi<endOfPageIndex;pi++) {
  838.       Page* page=pi->Screen(findDigest1,findDigest2);
  839.       if (page) {
  840.         ulong acc=page->Match(sig);
  841.         if (acc) numMatch=page->SendSelection(
  842.           acc,findString,program,matchList,numMatch);
  843.       }
  844.     }
  845.     return numMatch;
  846.   }
  847. };
  848.  
  849. void InitDisambiguator(
  850. // InitDisambiguator to be called from the application
  851.   CCC *const wordList[],
  852.   ulong numWords,
  853.   void *privStorage,
  854.   ulong storageSize
  855. ) {
  856. // Just sets up the private data structure
  857.   PrivateData* PD=(PrivateData*)privStorage;
  858.   PD->Init(wordList,numWords,storageSize);
  859. }
  860.  
  861. ulong Disambiguator(
  862. // Disambiguator to be called from the application
  863.   CCC *const wordList[],
  864.   ulong numWords,
  865.   void *privStorage,
  866.   ulong storageSize,
  867.   char *findString,
  868.   CCC *matchList[]
  869. ) {
  870.   PrivateData* PD=(PrivateData*)privStorage;
  871.  
  872. // Allocates space for the longest possible VM program on
  873. // the stack and parses find string
  874.   Opcode program[MAX_LEN];
  875.   int useVM=Parse(findString,program);
  876.  
  877. // findstring is now stripped of wildcards
  878.   ulong numMatch;
  879.  
  880.   if (PD->IsInitialized()) {
  881. // should always be true except ..
  882.     if ((*findString) || (program[0]>31)) {
  883. // Normal findString with alphanum characters
  884. // or wildCards only, but minLength > 31
  885.         
  886.       if (useVM) {
  887. // wild cards detected, must use VM matching
  888.         numMatch=PD->CollectVM(findString,matchList,program);
  889.  
  890.       } else {
  891. // no wild cards, can use faster simple matching function
  892.         numMatch=PD->Collect(findString,matchList,program);
  893.       }
  894.  
  895.     } else {
  896. // No characters to match, minimum length <= 31
  897. // we can simply send all >= minimum length
  898.       numMatch=PD->SendAll(matchList,program[0]);
  899.     }
  900.  
  901.   } else {
  902. // if we get here, PD was not initialized, and we have to
  903. // just scan the entire word list for matches
  904. // We don't really expect to get here except with
  905. // extremely short word lists.
  906.     numMatch=0;
  907.     if ((*findString) || (program[0]>31)) {
  908.       if (useVM) {
  909.         for (ulong i=0;i<numWords;i++)
  910.           if (SUCCEED==
  911.             VMMatch(wordList[i],strlen(wordList[i]),
  912.                 findString,program))
  913.             Send(matchList,wordList[i],numMatch++);
  914.       } else {
  915.         for (ulong i=0;i<numWords;i++)
  916.           if (SUCCEED==
  917.             SimpleMatch(wordList[i],findString,program[0]))
  918.             Send(matchList,wordList[i],numMatch++);
  919.       }
  920.     } else {
  921.       for (ulong i=0;i<numWords;i++)
  922.         if (strlen(wordList[i]) >= program[0])
  923.           Send(matchList,wordList[i],numMatch++);
  924.     }
  925.   }
  926.  
  927. // Final sort (step 2 of heap sort) for match list
  928.   Sort(matchList,numMatch);
  929.   return numMatch;
  930. }
  931.  
  932.  
  933. /******* Static Functions **********/
  934.  
  935. static int SimpleMatch(
  936.         CCC* x,
  937.         char* findString,
  938.         int minLen) {
  939. // alphanumeric matche of x against findString,
  940. // no wild cards allowed
  941.   x--;findString--;
  942.   for (int i=0;i<minLen;i++) {
  943.     if (MISMATCH(*++x,*++findString)) return FAIL;
  944.   }
  945.   return SUCCEED;
  946. }
  947.  
  948. static void Send(
  949. // Inserts word wp in matchList as a priority queue
  950. // in preparation for sorting later
  951.   CCC* matchList[],
  952.   CCC* wp,
  953.   ulong numMatch) {
  954.   CCC** base=matchList-1;
  955.   CCC* z;
  956.   long i=numMatch+1,j=i>>1;
  957.   while ((j>0)&&Comp(wp,z=base[j])>0) {
  958.     base[i]=z;
  959.     i=j;
  960.     j=i>>1;
  961.   }
  962.   base[i]=wp;
  963. }
  964.  
  965. static int Comp(CCC* ap,CCC* bp) {
  966. // Alphabetic case insensitive string comparator
  967.   char a=*ap;
  968.   if (a) do {
  969.     char b=*bp;
  970.     if (MISMATCH(a,b)) {
  971.       return (a | 0x20) - (b | 0x20);
  972.     }
  973.     a=*++ap;
  974.     bp++;
  975.   } while (a);
  976.   return -1;
  977. }
  978.  
  979. static int Parse(char* findString,Opcode program[]) {
  980. // Scans the findString and creates a bytecode program.
  981. // All wild cards are stripped from findString.
  982. // program[0] contains the minimum length of words to match,
  983. // the rest of program[] contains tokens from the enum set.
  984. // Returns the number of wild cards removed from findString.
  985. #define EMIT(x) {*++pgm=x;}
  986.   Opcode* pgm=program;
  987.   char* newFindString=findString;
  988.   int onFailure=FAIL;
  989.   char c=*findString;
  990.   int n,minLen=0,usesWild=0;
  991.   for (;;) {
  992.     if (0==charTable[c])
  993.     switch(c) {
  994. case '?':
  995.       n=0;
  996.       do {n++;} while ('?'==(c=*++findString));
  997.       if (n==1) EMIT(READ1) else {
  998.         EMIT(READ);
  999.         EMIT(n);
  1000.       }
  1001.       minLen+=n;
  1002.       usesWild++;
  1003.       break;
  1004. case '+':
  1005.       minLen++;
  1006.       EMIT(READ1);
  1007. case '*':
  1008.       c=*++findString;
  1009.       onFailure=RETRY;
  1010.       usesWild++;
  1011.       break;
  1012. default:
  1013.       EMIT(SUCCEED);
  1014.       *newFindString=0;  //zero terminate the new FindString
  1015.       program[0]=minLen;
  1016.       return usesWild;
  1017.     } else {
  1018.       n=0;
  1019.       do {
  1020.         n++;
  1021.         *newFindString++=c;    // copy non-wilds to new string
  1022.       } while (charTable[c=*++findString]);
  1023.       if (FAIL==onFailure) {
  1024.         if (n<=7) EMIT(COMPF+n) else {
  1025.           EMIT(COMPF);
  1026.           EMIT(n);
  1027.         }
  1028.       } else {
  1029.         if (n<=7) EMIT(COMPR+n) else {
  1030.           EMIT(COMPR);
  1031.           EMIT(n);
  1032.         }
  1033.       }
  1034.       onFailure=FAIL;
  1035.       minLen+=n;
  1036.     }
  1037.   }
  1038. }
  1039.  
  1040. static ulong MakeSubDigest(
  1041.         CCC* xString,
  1042.         ulong* dig2,
  1043.         char sig[]) {
  1044. // Creates a pair of word digests and a signature from
  1045. // findString.  No duplicate signature characters.
  1046.   int s=HASH(xString[0]);
  1047.   ulong digest1=1L<<s;
  1048.   ulong multi=0;
  1049.   for (;;) {
  1050.     int c;
  1051.     if (0==(c=*++xString)) break;
  1052.     s=HASH(c);
  1053.     ulong bit=1L<<s;
  1054.     if (0==(digest1 & bit)) {
  1055.       digest1 |= bit;
  1056.     } else {
  1057.       if (0==(multi & bit)) {
  1058.         multi |= bit;
  1059.       }
  1060.     }
  1061.   }
  1062. // Make the signature in bit order, that is with the
  1063. // less frequent English letters first, so that page
  1064. // scanning will fail as soon as possible if no word
  1065. // in the page will match.
  1066.   ulong bit1=2,bit2=2;
  1067.   ulong single=digest1 & (~multi);
  1068.   int s1,s2;
  1069.  
  1070. // Test rarest letter combinations first
  1071.   if (multi) {
  1072.     for (s2=29;s2<=56;s2++) {
  1073.       if (multi & bit2) *sig++=s2;
  1074.       bit2 += bit2;
  1075.     }
  1076.   }
  1077.  
  1078.   for (s1=1;s1<=28;s1++) {
  1079.     if (single & bit1) *sig++=s1;
  1080.     bit1 += bit1;
  1081.   }
  1082.   *sig++=0;
  1083.   *dig2=multi;
  1084.   return digest1;
  1085. }
  1086.  
  1087. static void Sort(CCC* matchList[],long numMatch) {
  1088. // Heap sort step 2, used for final sorting of the
  1089. // match list.
  1090.   CCC** sList=matchList-1;
  1091.   CCC* x;
  1092.   int  i,j;
  1093.   CCC** b=sList+numMatch+1;
  1094.   if (numMatch>1) do {
  1095.     i=1;j=2;
  1096.     x=sList[numMatch--];
  1097.     *(--b) = sList[1];
  1098.     if (numMatch<=1) {
  1099.       sList[1]=x;
  1100.       break;
  1101.     }
  1102.     while (j<=numMatch) {
  1103.       if ((j<numMatch)
  1104.       &&  (Comp(sList[j],sList[j+1])<0))
  1105.         j++;
  1106.       if (Comp(x,sList[j])>=0)
  1107.         break;
  1108.       sList[i]=sList[j];
  1109.       i=j;j+=j;
  1110.     }
  1111.     sList[i]=x;
  1112.   } while(1);
  1113. }
  1114.  
  1115.  
  1116.  
  1117.